package com.mengxin.secondkill.util;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author mengxin
 * @date 2020/12/25 14:36
 */
@Component
public class SonwFlakeKeyGenerator {

    /**
     * 起始的时间戳
     */
    private final static long START_STMP = 1576477053000L;

    /**
     * 每一部分占用的位数
     */
    // 序列号
    private final static long SEQUENCE_BIT = 12;

    // 机器标识
    private final static long MACHINE_BIT = 5;

    // 应用标识
    private final static long DATACENTER_BIT = 5;

    /**
     * 每一部分的最大值
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);

    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);

    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;

    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;

    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    @Value("${snow-flake-key.datacenter-id}")
    public String datacenterId;
    @Value("${snow-flake-key.machine-id}")
    public String machineId;

    private long sequence = 0L;

    volatile long lastStmp = -1L;


    /**
     * 产生下一个ID
     *
     * @return
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currStmp == lastStmp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            sequence = 0L;
        }
        lastStmp = currStmp;
        return (currStmp - START_STMP) << TIMESTMP_LEFT
                | Long.valueOf(datacenterId) << DATACENTER_LEFT
                | Long.valueOf(machineId) << MACHINE_LEFT
                | sequence;
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }
}
